import java.io.File;
import java.sql.SQLException;
import java.util.Scanner;
import java.util.StringTokenizer;
import java.util.Vector;
import javax.swing.JOptionPane;
import javax.swing.UIManager;

/**
 * This class collapses a superclass and the specified subclass into one class
 * @author Vincent Bantjes
 */
public class Collapse_Hierarchy_Generalization_Refactor implements RefactoringMethod
{
    private String superclass = "";
    private String subclass = "";
    private Vector errors = new Vector(0);
    private String superClassName = "";
    private String subClassName = "";
    private int superclassDeclerationLineNumber = -1;
    Vector<String> refactoredClasses = new Vector<String>(0);
    private CollapsePreConditions preconditions = null;
    File[] files = null;
    Vector<String> paths = new Vector<String>();
    Vector<String> allClassNames = new Vector<String>();
    Vector<String> allClasses = new Vector<String>();
    Vector<String> oldMethods = new Vector<String> ();
    Vector<String> newMethods = new Vector<String> ();
    Vector<String> oldFields = new Vector<String> ();
    Vector<String> newFields = new Vector<String> ();

    /**
     * The constructor that receives first the superclass, then the subclass, then a boolean value stating which class should be refactored to.
     */
    public Collapse_Hierarchy_Generalization_Refactor() 
    {
        preconditions = new CollapsePreConditions();
        try 
        {
            preconditions.connect();
        }
        catch (Exception ex) 
        {
            JOptionPane.showMessageDialog(null, ex.toString(), "Collapse Hierarchy Preconditions", JOptionPane.ERROR_MESSAGE);
        }
    }

    /**
     * The method that is called to execute the refactoring.
     * @param parameters Vector<String>. The parameters containing the classpath.
     * @return Vector<String>. Returns the class name and the class contents for all of classes that were changed.
     */
    @Override
    public Vector<String> refactor(Vector<String> parameters)
    {
        try
        {
            try 
            {
                UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            } 
            catch (Exception ex) 
            {
                refactoredClasses.add("Error");
                refactoredClasses.add("Error, "+ex);
                preconditions.disconnect();
                return refactoredClasses;
            }

            if(!parameters.isEmpty())
            {
                String path = "";
                
                StringTokenizer tokens = new StringTokenizer(parameters.get(0),"/");

                while(tokens.countTokens() > 1)
                {
                    path += tokens.nextToken() + "/";
                }
                superClassName = tokens.nextToken();//get the classname.java part
                superClassName = superClassName.substring(0,superClassName.lastIndexOf("."));//get the classname part

                //get subclasses
                File filesPath = new File(path);
                files = filesPath.listFiles(new WildCardFileFilter("*.java"));
                if(files.length <= 0)
                {
                    refactoredClasses.add("Error");
                    refactoredClasses.add("Error, There were no files in the directory.");
                    preconditions.disconnect();
                    return refactoredClasses;
                }
                else
                {
                    String wholePath = path + superClassName + ".java";
                    for(File oneFile : files)
                    {
                        String oneFilePath = oneFile.getPath();
                        while(oneFilePath.contains("\\"))
                        {
                            oneFilePath = oneFilePath.replace("\\", "/");
                        }
                        if(!wholePath.equals(oneFilePath))
                        {
                            paths.add(oneFilePath);//all the other paths exept the superclass path.
                        }
                    }

                    Vector<String> subClassNames = new Vector<String>();
                    FileEditor editor = new FileEditor();
                    String fileContents = "";
                    for(int j = 0; j < paths.size(); j++)
                    {
                        try
                        {
                            String className = paths.get(j).substring(paths.get(j).lastIndexOf("/") + 1, paths.get(j).lastIndexOf("."));
                            allClassNames.add(className);
                            fileContents = editor.read(paths.get(j));
                            allClasses.add(fileContents);
                            if(preconditions.isSubclass(superClassName, fileContents))
                            {
                                    subClassNames.add(className);
                            }
                        } 
                        catch (Exception ex) 
                        {
                            refactoredClasses.add("Error");
                            refactoredClasses.add("Error, An error occured reading the classes." + ex);
                            preconditions.disconnect();
                            return refactoredClasses;
                        }
                    }

                    //ask user which subclass
                    Object[] selectionValues = new Object[subClassNames.size()];
                    for(int i = 0; i < subClassNames.size(); i++)
                    {
                        selectionValues[i] = subClassNames.get(i);
                    }

                    if(subClassNames.isEmpty())
                    {
                        refactoredClasses.add("Error");
                        refactoredClasses.add("Error, Superclass does not have a subclass.");
                        preconditions.disconnect();
                        return refactoredClasses;
                    }
                    
                    subClassName = JOptionPane.showInputDialog(null, "Please select the subclass?", "Subclass Selection", JOptionPane.QUESTION_MESSAGE, null, selectionValues,"").toString();

                    for(int i = 0; i < allClasses.size(); i++)
                    {//need to check for other datastructures
                        if(allClasses.get(i).contains("<"+subClassName+">") || allClasses.get(i).contains(subClassName + "["))
                        {
                            refactoredClasses.add("Error");
                            refactoredClasses.add("Error, Preconditions were not met. No class can have a data structure that stores elements\nof type "+subClassName+". It doesn't matter if it is commented out or not.");
                            preconditions.disconnect();
                            return refactoredClasses;
                        }
                    }
                    try
                    {
                        Database db = new Database();
                        db.connect();
                        db.initializeDB();
                        db.disconnect();

                        ParserProxy parser = new ParserProxy("FileParser");
                        parser.parseString(path+superClassName+".java");

                        ParserProxy parser2 = new ParserProxy("FileParser");
                        parser2.parseString(path+subClassName+".java");
                        
                        boolean superclassAbstract = preconditions.abstractCheck(superClassName);
                        boolean subclassAbstract = preconditions.abstractCheck(subClassName);

                        if(superclassAbstract && !subclassAbstract)
                        {
                            refactoredClasses.add("Error");
                            refactoredClasses.add("Error, The superclass is abstract. The refactoring cannot be done if only one of the classes is abstract.");
                            preconditions.disconnect();
                            return refactoredClasses;
                        }
                        else if(!superclassAbstract && subclassAbstract)
                        {
                            refactoredClasses.add("Error");
                            refactoredClasses.add("Error, The subclass is abstract. The refactoring cannot be done if only one of the classes is abstract.");
                            preconditions.disconnect();
                            return refactoredClasses;
                        }

                        boolean superclassInterface = preconditions.interfaceCheck(superClassName);
                        boolean subclassInterface = preconditions.interfaceCheck(subClassName);

                        if(superclassInterface || subclassInterface)
                        {
                            refactoredClasses.add("Error");
                            refactoredClasses.add("Error, Precondition not met. The refactoring cannot be done if any of the classes is an interface.");
                            preconditions.disconnect();
                            return refactoredClasses;
                        }
                    }
                    catch(Exception e)
                    {
                        refactoredClasses.add("Error");
                        refactoredClasses.add("Error, An error occured in reading the file to the database.");
                        preconditions.disconnect();
                        return refactoredClasses;
                    }
                    
                    try 
                    {
                        FileEditor reader = new FileEditor();
                        superclass = reader.read(path+"/"+superClassName+".java");
                        superclass = superclass.trim();
                        subclass = reader.read(path+"/"+subClassName+".java");
                        subclass = subclass.trim();

                    }
                    catch (Exception e1) 
                    {
                        refactoredClasses.add("Error");
                        refactoredClasses.add("Error, An error occured reading the superclass/subclass." + e1);
                        preconditions.disconnect();
                        return refactoredClasses;
                    }

                    //The class that was refactored to.
                    String refactoredClass = "";

                    boolean continueWithRefactoring = false;
                    try
                    {
                        continueWithRefactoring = tests();
                    }
                    catch (Exception ex) 
                    {
                        refactoredClasses.add("Error");
                        refactoredClasses.add("Error, An error occured in test()." + ex);
                        preconditions.disconnect();
                        return refactoredClasses;
                    }

                    if (!continueWithRefactoring)
                    {
                        refactoredClasses.add("Error");
                        refactoredClasses.add("Error, Preconditions for this refactoring method are not met.\nCanceling refactoring");
                        preconditions.disconnect();
                        return refactoredClasses;
                    } 
                    else 
                    {
                        //Errors
                        if (!errors.isEmpty())
                        {
                            String newImplements = "";
                            String newImports = "";
                            Vector<Integer> constructors = new Vector<Integer>(0);

                            //Keep the superclass
                            for (int i = 0; i < errors.size(); i++)
                            {
                                if (errors.get(i).getClass() == new MethodClash("", "", "").getClass()) 
                                {
                                    MethodClash clash = (MethodClash) errors.get(i);

                                    int lineNumber = Integer.parseInt(clash.getSubLineNum());

                                    subclass = handleMethodClash(subclass, clash.getMethodName() + "(", lineNumber);
                                    
                                    if(subclass.equals("An error occured in handleClash (1)") || subclass.equals("An error occured in handleClash (2)"))
                                    {
                                        refactoredClasses.add("Error");
                                        refactoredClasses.add("Error, " + subclass);
                                        preconditions.disconnect();
                                        return refactoredClasses;
                                    }
                                } 
                                else if(errors.get(i).toString().contains(";;implements;;"))
                                {
                                    newImplements = errors.get(i).toString().substring(15);
                                } 
                                else if (errors.get(i).toString().contains(";;imports;;"))
                                {
                                    newImports = errors.get(i).toString().substring(12);
                                } 
                                else  if (errors.get(i).getClass() == new FieldClash("", "", "").getClass())
                                {
                                    FieldClash clash = (FieldClash) errors.get(i);
                                    subclass = handleFieldClash(subclass, clash.getFieldName());
                                } 
                                else if (errors.get(i).getClass() == new Vector<Integer>().getClass())
                                {
                                    constructors = (Vector<Integer>) errors.get(i);
                                    for (int j = 0; j < constructors.size(); j++) 
                                    {
                                        subclass = handleConstructor(subclass, subClassName, constructors.get(j));
                                        
                                        if(subclass.equals("An error occured in handleConstructor"))
                                        {
                                            refactoredClasses.add("Error");
                                            refactoredClasses.add("Error, " + subclass + constructors.get(j));
                                            preconditions.disconnect();
                                            return refactoredClasses;
                                        }
                                    }
                                }
                            }
                            
                            Vector<String> variablesReferenced = new Vector<String>();
                            referenceFinder finder = new referenceFinder();
                            for(int i = 0; i < allClasses.size(); i++)
                            {
                                if(!allClassNames.get(i).equals(subClassName))
                                {
                                    variablesReferenced = finder.getVariableNames(subClassName, superClassName, allClasses.get(i));

                                    if(variablesReferenced.size() > 0)
                                    {

                                        allClasses.set(i, finder.renameMethod(allClasses.get(i), variablesReferenced, oldMethods, newMethods));
                                        allClasses.set(i, finder.renameField(allClasses.get(i), variablesReferenced, oldFields, newFields));
                                        allClasses.set(i, finder.renameConstructor(subClassName, superClassName, allClasses.get(i)));

                                        refactoredClasses.add(paths.get(i));
                                        refactoredClasses.add(allClasses.get(i));
                                    }
                                }
                            }
                            Scanner superClassTokens = new Scanner(superclass);
                            superClassTokens.useDelimiter("\n");

                            if (superClassTokens.hasNext()) 
                            {
                                String line = superClassTokens.next();

                                int j = 1;
                                while (!line.contains("import") && superClassTokens.hasNext() && j < superclassDeclerationLineNumber)
                                {
                                    refactoredClass += line + "\n";
                                    line = superClassTokens.next();
                                    j++;
                                }

                                if (!newImports.equals("")) 
                                {
                                    refactoredClass += newImports;
                                }

                                while (j < superclassDeclerationLineNumber) 
                                {
                                    refactoredClass += line + "\n";
                                    line = superClassTokens.next();
                                    j++;
                                }

                                if (!newImplements.equals(""))
                                {
                                    Scanner lineTokens = new Scanner(line);
                                    lineTokens.useDelimiter(" ");
                                    line = handleImplements(lineTokens, newImplements);
                                    
                                    if(line.equals("An error occured in handleImplements"))
                                    {
                                        refactoredClasses.add("Error");
                                        refactoredClasses.add("Error, " + line);
                                        preconditions.disconnect();
                                        return refactoredClasses;
                                    }
                                }

                                refactoredClass += line + "\n";

                                while (superClassTokens.hasNext()) 
                                {
                                    line = superClassTokens.next();
                                    refactoredClass += line + "\n";
                                }

                                //at this point all of the errors are handled, so we can just call the method that handles the ideal case
                                refactoredClass = refactorIdeal(refactoredClass, subclass);
                            }
                        } //Ideal circumstances
                        else
                        {
                            refactoredClass = refactorIdeal(superclass, subclass);
                        }
                        refactoredClasses.add(0,path + superClassName + ".java");
                        refactoredClasses.add(1,refactoredClass);
                        preconditions.disconnect();
                        return refactoredClasses;
                    }
                }//if there are files in the directory
            }
            else
            {
                refactoredClasses.add("Error");
                refactoredClasses.add("Error, An error occured in the parameter passing.");
                preconditions.disconnect();
                return refactoredClasses;
            }
        }
        catch(Exception e)
        {
            refactoredClasses.add("Error");
            refactoredClasses.add("Error, An unexpedted error occured." + e);
            try 
            {
                preconditions.disconnect();
            } 
            catch (SQLException ex)
            {
                JOptionPane.showMessageDialog(null, ex.toString(), "Collapse Hierarchy Preconditions", JOptionPane.ERROR_MESSAGE);
            }
            return refactoredClasses;
        }
    }

    /**
     * This method handles a method/field clash by replacing the clashing name in the specified parameter with another name. 
     * @param Class String. The class in which the clashing name should be changed.
     * @param clash String. The method/field that is clashing and needs to be changed.
     * @param lineNumber int. The line where the clash occurred.
     * @return String. This method return the class that was sent in, but with the clashing method name being changed.
     */
    public String handleMethodClash(String Class, String clash, int lineNumber)
    {
        oldMethods.add(clash.substring(0,clash.length()-1));
        newMethods.add("deletedClass_"+clash.substring(0,clash.length()-1)); 
        
        String newClass = "";
        Scanner classTokens = new Scanner(Class);
        classTokens.useDelimiter("\n");

        String line = classTokens.next();
        
        for (int i = 1; i < lineNumber; i++) 
        {
            newClass += line + "\n";
            line = classTokens.next();
        }
        
        if (line.contains(clash)) 
        {
            StringTokenizer tokens = new StringTokenizer(line);

            if (tokens.hasMoreTokens())
            {
                String word = tokens.nextToken();

                String newLine = "";

                while (!word.equals(clash) && !word.startsWith(clash) && !word.endsWith(clash) && ! word.contains(clash))
                {
                    newLine += word + " ";
                    word = tokens.nextToken();
                }
                
                if (word.startsWith(clash))
                {
                    word = "deletedClass_" + word + " ";
                } 
                else if(word.endsWith(clash))
                {
                    word = word.substring(0,word.indexOf(clash)) + "deletedClass_" + clash + " ";
                }
                else if(word.contains(clash))
                {
                    String firstPart = word.substring(0,word.indexOf(clash));
                    String secondPart = word.substring(word.indexOf(clash));
                    
                    word = firstPart + "deletedClass_" + secondPart + " ";
                }
                else 
                {
                    return "An error occured in handleMethodClash (1)";
                }

                newLine += word;

                while (tokens.hasMoreTokens()) 
                {
                    newLine += tokens.nextToken() + " ";
                }

                newClass += newLine + "\n";
            }
        } 
        else
        {
            return "An error occured in handleMethodClash (2)";
        }

        while (classTokens.hasNext())
        {
            line = classTokens.next();
            newClass += line + "\n";
        }
       
        return newClass;
    }
    
    /**
     * This method handles a method/field clash by replacing the clashing name in the specified parameter with another name. 
     * @param Class String. The class in which the clashing name should be changed.
     * @param clash String. The method/field that is clashing and needs to be changed.
     * @return String. This method return the class that was sent in, but with the clashing method name being changed.
     */
    public String handleFieldClash(String Class, String clash)
    {
        oldFields.add(clash);
        newFields.add("deletedClassField_"+clash);

        String newClass = "";
        Scanner classTokens = new Scanner(Class);
        classTokens.useDelimiter("\n");

        boolean checkNextLine = false;
        while(classTokens.hasNext())
        {
            String line = classTokens.next();
            
            if(line.startsWith("//"))
            {
                newClass += line + "\n";
            }
            else if(line.startsWith("/*"))
            {
                if(line.contains("*/"))
                {
                    newClass += line.substring(0,line.indexOf("*/"));
                    line = line.substring(line.indexOf("*/"));
                    newClass += handleLine(line, clash);
                }
                else
                {
                    newClass += line + "\n";
                    checkNextLine = true;
                }
            }
            else if(line.contains("/*"))
            {
                if(line.endsWith("*/"))
                {
                    String comment = line.substring(line.indexOf("/*"));
                    line = line.substring(0,line.indexOf("/*"));
                    newClass += handleLine(line, clash) + comment + "\n";
                }
                else if(line.contains("*/"))
                {
                    String firstPart = line.substring(0,line.indexOf("/*"));
                    String comment = line.substring(line.indexOf("/*"),line.indexOf("*/")+2);
                    line = firstPart + line.substring(line.indexOf("*/")+2);
                    newClass += handleLine(line, clash) + comment + "\n";
                }
                else
                {
                    String firstPart = line.substring(0,line.indexOf("/*"));
                    newClass += handleLine(firstPart, clash) + line.substring(line.indexOf("/*"))+ "\n";
                    checkNextLine = true;
                }
            }
            else if(checkNextLine == true)
            {
                if(line.endsWith("*/"))
                {
                    checkNextLine = false;
                    newClass += line + "\n";
                }
                else if(line.contains("*/"))
                {
                    newClass += line.substring(0,line.indexOf("*/") + 2);
                    line = line.substring(line.indexOf("*/")+2);
                    newClass += handleLine(line, clash);
                    checkNextLine = false;
                }
                else
                {
                    newClass += line + "\n";
                }
            } 
            else
            {
                newClass += handleLine(line, clash);
            }
        }
        return newClass;
    }
    
    /**
     * This method checks if a clashing field name occurs on the given line, and then renames the clashing field name.
     * @param line String. The line that needs to be handled.
     * @param clash String. The clashing field that needs to be changed.
     * @return 
     */
    public String handleLine(String line, String clash)
    {
        if(line.contains(clash))
        {
            StringTokenizer tokens = new StringTokenizer(line);

            String newLine = "";

            while(tokens.hasMoreTokens())
            {
                String word = tokens.nextToken();

                while (!word.contains(clash) && tokens.hasMoreTokens())
                {
                    newLine += word + " ";
                    word = tokens.nextToken();
                }

                characterList list = new characterList();

                if(!list.checkKeywords(word))
                {
                    if(word.equals(clash))
                    {
                        word = " deletedClassField_" + clash;
                    }
                    else if (word.startsWith(clash))
                    {
                        if(word.charAt(clash.length()) != '(')
                        {
                            String lowercase = Character.toString(word.charAt(clash.length())).toLowerCase();
                            
                            if(checkIfWordIsValid(lowercase) == true)
                            {
                                word = " deletedClassField_" + word + " ";
                            }
                        }
                    } 
                    else if(word.endsWith(clash))
                    {
                        String lowercase = Character.toString(word.charAt(word.indexOf(clash)-1)).toLowerCase();
                            
                        if(checkIfWordIsValid(lowercase) == true)
                        {
                            word = word.substring(0,word.indexOf(clash)) + " deletedClassField_" + clash;
                        }
                    }
                    else if(word.contains(clash))
                    {
                        if(word.charAt(word.indexOf(clash) + clash.length()) != '(')
                        {
                            String lowercase = Character.toString(word.charAt(clash.length())).toLowerCase();
                            
                            if(checkIfWordIsValid(lowercase) == true)
                            {
                                lowercase = Character.toString(word.charAt(word.indexOf(clash)-1)).toLowerCase();
                            
                                if(checkIfWordIsValid(lowercase) == true)
                                {
                                    String firstPart = word.substring(0,word.indexOf(clash));
                                    String secondPart = word.substring(word.indexOf(clash));
                                    word = firstPart + " deletedClassField_" + secondPart;
                                }
                            }
                        }
                    }
                }
                newLine += word + " ";
            }
            return newLine + "\n";
        }
        else
        {
            return line + "\n";
        }
    }
    
    /**
     * This method checks if the character given is one of the invalid characters. Them being alphabet characters, numbers , _ or $.
     * @param lowercase String. The character represented as a string.
     * @return boolean. True if the character is not one of the invalid characters, and false if it is.
     */
    public boolean checkIfWordIsValid(String lowercase)
    {
        characterList list = new characterList();
        Vector<String> alphabet = list.alphabet();
        for(int i = 0; i < alphabet.size(); i++)
        {
            if(lowercase.equals(alphabet.get(i)))
            {
                return false;
            }
        }
        if(lowercase.equals("0"))
        {
            return false;
        }
        if(lowercase.equals("1"))
        {
            return false;
        }
        if(lowercase.equals("2"))
        {
            return false;
        }
        if(lowercase.equals("3"))
        {
            return false;
        }
        if(lowercase.equals("4"))
        {
            return false;
        }
        if(lowercase.equals("5"))
        {
            return false;
        }
        if(lowercase.equals("6"))
        {
            return false;
        }
        if(lowercase.equals("7"))
        {
            return false;
        }
        if(lowercase.equals("8"))
        {
            return false;
        }
        if(lowercase.equals("9"))
        {
            return false;
        }
        if(lowercase.equals("_"))
        {
            return false;
        }
        if(lowercase.equals("$"))
        {
            return false;
        }
        return true;
    }

    /**
     * This method handles an implements issue. It sets the implements of the new class after the refactoring to the combination of the two classes before the refactoring.  
     * @param lineTokens Scanner. The new class's line that will be updated.
     * @param newImplements String. The implements of both the classes before the refactoring.
     * @return String. The line in which the replacement is already done.
     */
    public String handleImplements(Scanner lineTokens, String newImplements)//, int lineNumber)
    {
        String newLine = "";

        if (lineTokens.hasNext()) 
        {
            newLine = lineTokens.next() + " ";
            boolean flag = false;
            while (lineTokens.hasNext())
            {
                if(!newLine.contains("implements"))
                {
                    String token = lineTokens.next();
                    if (token.contains("\r"))
                    {
                        token = token.replace("\r", "");
                    }
                    newLine += token + " ";
                }
                else
                {
                    String token = lineTokens.next();
                    if(token.contains("{"))
                    {
                        flag = true;
                    }
                }
            }

            if(newLine.contains("implements")) 
            {
                if(flag == true)
                {
                    newLine += newImplements + " {";
                }
                else
                {
                    newLine += newImplements + " ";
                }
            } 
            else 
            {
                if(newLine.contains("{"))
                {
                    newLine = newLine.replace("{", "");
                    newLine += "implements " + newImplements + " {";
                }
                else
                {
                    newLine += "implements " + newImplements + " ";
                }
            }
        } 
        else 
        {
            return "An error occured in handleImplements";
        }
        return newLine;
    }

    /**
     * This method handles a constructor issue. It gets the contents of the   
     * @param Class String. The class that will be updated.
     * @param constructorName String. The constructorName.
     * @param lineNumber
     * @return String. The line in which the replacement is already done.
     */
    public String handleConstructor(String Class, String constructorName, int lineNumber)
    {        
        String newClass = "";
        Scanner classTokens = new Scanner(Class);
        classTokens.useDelimiter("\n");

        String line = classTokens.next();

        for (int i = 1; i < lineNumber; i++)
        {
            newClass += line + "\n";
            line = classTokens.next();
        }
        
        if (line.contains(constructorName)) 
        {
            Scanner tokens = new Scanner(line);

            if (tokens.hasNext()) 
            {
                String word = tokens.next();

                String newLine = "";

                while (!word.contains(constructorName) && tokens.hasNext()) 
                {
                    newLine += word + " ";
                    word = tokens.next();
                }
                
                if (word.startsWith(constructorName + "("))
                {
                    word = "void " + word + " ";
                }
                else if(word.endsWith(constructorName + "("))
                {
                    word = word.substring(0,word.indexOf(constructorName + "(")) + "void " + constructorName + "( ";
                }
                else if(word.contains(constructorName))
                {
                    word = word.substring(0,word.indexOf(constructorName + "(")) + "void " + constructorName + "(" + word.substring(word.indexOf(constructorName + "(") + constructorName.length() + 1) + " ";
                }
                
                newLine += word;

                while (tokens.hasNext()) 
                {
                    newLine += tokens.next() + " ";
                }

                newClass += newLine + "\n";
            }
        } 
        else 
        {
            return "An error occured in handleConstructor";
        }

        while (classTokens.hasNext()) 
        {
            line = classTokens.next();
            newClass += line + "\n";
        }
        
        return newClass;
    }

    /**
     * This method is used in the ideal case where there are no errors between the classes, and the refactoring can just be done.
     * @param classToKeep String. A string representation of the class that you want to keep.
     * @param classToRemove String. A string representation of the class that you want to remove.
     */
    public String refactorIdeal(String classToKeep, String classToRemove)
    {
        ExtractClassContents extract = new ExtractClassContents();
        
        String classToRemoveContents = extract.getClassContent(classToRemove);
        
        Vector<String> splitClassToKeep = extract.getSeparatedClassContent(classToKeep);
        
        classToKeep = splitClassToKeep.get(0);
        
        classToKeep = classToKeep.substring(0, classToKeep.lastIndexOf("}"));

        classToKeep += classToRemoveContents.substring(classToRemoveContents.indexOf("{")+1,classToRemoveContents.lastIndexOf("}")) + "\n" + "}" + splitClassToKeep.get(1);

        return classToKeep;
    }

    /**
     * This method handles precondition testing, and testing for errors that can occur during the refactoring.
     * @throws ClassNotFoundException 
     * @throws IllegalAccessException 
     * @throws InstantiationException 
     * @throws SQLException 
     */
    @SuppressWarnings("unchecked")
    public boolean tests() throws SQLException, InstantiationException, IllegalAccessException, ClassNotFoundException
    {
        if (preconditions.subExtendsSuperClass(superClassName, subClassName))
        {
            superclassDeclerationLineNumber = preconditions.getClassLineNum(superClassName);
            
            Vector<MethodClash> methodClashList = new Vector<MethodClash>(0);
            methodClashList = preconditions.methodClash(superClassName, subClassName);

            //Method clashes
            for (int i = 0; i < methodClashList.size(); i++) 
            {
                MethodClash clash = methodClashList.get(i);
                errors.add(clash);
            }

            //Implements Issues
            Vector<String> superImplements = preconditions.getImplements(superClassName);
            Vector<String> subImplements = preconditions.getImplements(subClassName);

            for(int i = 0; i < subImplements.size(); i++)
            {
                for(int j = 0; j < superImplements.size(); j++)
                {
                    if(superImplements.get(j).equals(subImplements.get(i)))
                    {
                        return false;
                    }
                }
            }
            
            String implementsString = "";
            
            for(int i = 0; i < superImplements.size(); i++)
            {
                if (implementsString.equals("")) 
                {
                    implementsString += superImplements.get(i);
                } 
                else 
                {
                    implementsString += ", " + superImplements.get(i);
                }
            }
            
            for(int i = 0; i < subImplements.size(); i++)
            {
                if (implementsString.equals("")) 
                {
                    implementsString += subImplements.get(i);
                } 
                else 
                {
                    implementsString += ", " + subImplements.get(i);
                }
            }

            if (!implementsString.equals("")) 
            {
                errors.add(";;implements;; " + implementsString);
            }
            
            //Imports Issues

            Vector<String> superImports = preconditions.getImports(superClassName);
            Vector<String> subImports = preconditions.getImports(subClassName);

            Vector<String> superImportNames = new Vector<String>();
            Vector<String> subImportNames = new Vector<String>();
            
            Vector<String> superImportPackages = new Vector<String>();
            Vector<String> subImportPackages = new Vector<String>();
            
            String importName = "";
            
            for(int i = 0; i < superImports.size(); i++)
            {
                importName = superImports.get(i);
                superImportPackages.add(importName.substring(0,importName.lastIndexOf(".")));
                importName = importName.substring(importName.lastIndexOf(".")+1);
                superImportNames.add(importName);
            }
            
            for(int i = 0; i < subImports.size(); i++)
            {
                importName = subImports.get(i);
                subImportPackages.add(importName.substring(0,importName.lastIndexOf(".")));
                importName = importName.substring(importName.lastIndexOf(".")+1);
                subImportNames.add(importName);
            }
            
            for(int i = 0; i < subImportNames.size(); i++)
            {
                for(int j = 0; j < superImportNames.size(); j++)
                {
                    if(!superImportPackages.get(j).equals(subImportPackages.get(i)))
                    {
                        if(superImportNames.get(j).equals(subImportNames.get(i)))
                        {
                            return false;
                        }
                    }
                }
            }
            
            String importsString = "";
            for(int i = 0; i < superImports.size(); i++)
            {
                importsString += "import " + superImports.get(i) + ";\n";
            }
            
            for(int i = 0; i < subImports.size(); i++)
            {
                importsString += "import " + subImports.get(i) + ";\n";
            }

            if (!importsString.equals("")) 
            {
                errors.add(";;imports;; " + importsString);
            }

            //Constructor issues

            Vector<Integer> subclassConstructorLineNumbers = preconditions.constructorLineNum(subClassName);

            if (subclassConstructorLineNumbers.size() > 0) 
            {
                errors.add(subclassConstructorLineNumbers);
            }

            //fieldNameClashes
            Vector<FieldClash> fieldClashList = new Vector<FieldClash>(0);
            fieldClashList = preconditions.fieldClash(superClassName, subClassName);

            for (int i = 0; i < fieldClashList.size(); i++) 
            {
                FieldClash clash = fieldClashList.get(i);
                errors.add(clash);
            }
        }
        else 
        {
            return false;
        }
        return true;
    }

    /**
     * The description of the Collapse_Hierarchy_Generalization_Refactoring class.
     * @return String. The description.
     */
    @Override
    public String description() 
    {
        return "This refactoring method collapses a hierarchy of between a superclass and a subclass, to the superclass.";
    }
}